/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package newpeer;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Enumeration;
import java.util.HashMap;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.swing.JOptionPane;
import net.jxta.document.AdvertisementFactory;
import net.jxta.id.IDFactory;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.socket.JxtaServerSocket;

/**
 *
 * @author Sandra Liliana
 */
public class PeerServer implements Runnable {

    private transient PeerGroup netPeerGroup = null;
    public String SOCKETIDSTR = "urn:jxta:uuid-59616261646162614E5047205032503393B5C2F6CA7A41FBB0F890173088E79404";
    public static final long TAM_PAQUETES = 468;
    private Peer padre;
    private int NumClienteON = 0;
    private HashMap<String, String> listaPeers;
    private int ipaquete;

    public PeerServer(Peer p, PeerGroup netPeerGroup, String idPeer) {
        padre = p;
        this.netPeerGroup = netPeerGroup;
        this.SOCKETIDSTR = idPeer;
        listaPeers = new HashMap<String, String>();
    }

    public PipeAdvertisement createSocketAdvertisement() {
        PipeID socketID = null;


        try {
            socketID = (PipeID) IDFactory.fromURI(new URI(SOCKETIDSTR));
        } catch (URISyntaxException use) {
            use.printStackTrace();
        }
        PipeAdvertisement advertisement = (PipeAdvertisement) AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());

        advertisement.setPipeID(socketID);
        advertisement.setType(PipeService.UnicastType);
        advertisement.setName("Peer to peer socket");
        System.out.println("Saliendo del socket server");
        return advertisement;
    }

    public void run() {
        System.out.println("Starting ServerSocket");
        JxtaServerSocket serverSocket = null;
        try {
            serverSocket = new JxtaServerSocket(netPeerGroup, createSocketAdvertisement(), 10);
            serverSocket.setSoTimeout(0);
        } catch (IOException e) {
            System.out.println("failed to create a server socket");
            e.printStackTrace();
            System.exit(-1);
        }

        while (true) {
            try {
                System.out.println("Waiting for connections");
                Socket socket = serverSocket.accept();
                NumClienteON++;
                if (socket != null) {
                    System.out.println("New socket connection accepted");
                    Thread thread = new Thread(new ConnectionHandler(socket), "Connection Handler Thread");
                    thread.start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private class ConnectionHandler implements Runnable {

        Socket socket = null;

        ConnectionHandler(Socket socket) {
            this.socket = socket;
        }

        /**
         * Sends data over socket
         *
         * @param socket the socket
         */
        private void sendAndReceiveData(Socket socket) {
            try {
                long start = System.currentTimeMillis();
                KeyStore keyStore;
                KeyFactory keyFactory = null;
                // get the socket output stream
                OutputStream out = socket.getOutputStream();
                // get the socket input stream
                InputStream in = socket.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                //DESCARGAR_ARCHIVO;[nombreArchivo];[peerName]
                //RECUPERAR_DESCARGA_ARCHIVO;[nombreArchivo];[numPaquete]
                String mensaje = br.readLine();
                String peername = mensaje.split(";")[2];
                if (mensaje.startsWith("RECUPERAR_DESCARGA_ARCHIVO")) {

                    //TODO recuperar descarga de archivo
                    //Reanudar descarga desde el paquete que llega como parámetro

                    String nombreArchivo = mensaje.split(";")[1];
                    String numeroPaqueteRecuperar = mensaje.split(";")[2];

                    File archivoAMandar = new File(padre.RUTA_ARCHIVOS_COMPARTIDOS_DESCARGADOS, nombreArchivo);
                    FileInputStream fis = new FileInputStream(archivoAMandar);
                    long tamArchivo = archivoAMandar.length();
                    if (tamArchivo < TAM_PAQUETES) {
                        byte[] menor = new byte[(int) tamArchivo];
                        fis.read(menor);
                        out.write(menor);
                    } else {
                        long numeroPaquetes = tamArchivo / TAM_PAQUETES;
                        //  System.out.println("NUMPAQUETES: " + numeroPaquetes);
                        //Descartar los que ya se han mandado y empezar desde el paquete a recuperar
                        int inicio = Integer.parseInt(numeroPaqueteRecuperar);
                        for (int i = 0; i < inicio; i++) {
                            byte[] paq = new byte[(int) TAM_PAQUETES];
                            fis.read(paq);
                        }

                        for (; inicio < numeroPaquetes; inicio++) {
                            byte[] paq = new byte[(int) TAM_PAQUETES];
                            fis.read(paq);
                            out.write(paq);
                            //System.out.println("iteracion:" + i);
                        }
                        long loQuefalta = archivoAMandar.length() - (numeroPaquetes * TAM_PAQUETES);
                        if (loQuefalta > 0) {
                            byte[] falta = new byte[(int) loQuefalta];
                            fis.read(falta);
                            out.write(falta);
                        }
                    }
                    fis.close();
                    socket.close();
                    listaPeers.remove(peername);
                } else if (mensaje.startsWith("DESCARGAR_ARCHIVO") && !listaPeers.containsKey(peername) && NumClienteON <= Integer.parseInt(padre.UPLOADS_GLOBALES)) {
                    listaPeers.put(peername, peername);
                    //JOptionPane.showMessageDialog(padre, "Adjunto nuevo peer: " + peername);
                    String msnOK = "OK";
                    PrintWriter pw = new PrintWriter(out, true);
                    pw.println(msnOK);
                    String nombreArchivo = mensaje.split(";")[1];
                    //Enviar la llave al cliente para verificación de los mensajes
                    // Generar  llave secreta para  HMAC-MD5. Es el Digest para probar integridad
                    KeyGenerator kg = KeyGenerator.getInstance("HmacMD5");
                    SecretKey sk = kg.generateKey();
                    ObjectOutputStream oosSocket = new ObjectOutputStream(out);
                    oosSocket.writeObject(sk);
                    ObjectOutputStream oosArchivo = new ObjectOutputStream(new FileOutputStream(new File("C:/temp/llaveServidor.obj")));
                    oosArchivo.writeObject(sk);
                    oosArchivo.close();
                    JOptionPane.showMessageDialog(null, "Llave enviada");
                    Mac mac = Mac.getInstance("HmacMD5");
                    mac.init(sk);
                    keyStore = KeyStore.getInstance("PKCS12");
                    System.out.println("Haciendo Instance de KeyStore ");
                    FileInputStream file_inputstream = new FileInputStream("c:/temp/certifica/" + peername + ".p12");
                    System.out.println("El fileinput en certifica ");
                    keyStore.load(file_inputstream, peername.toCharArray());
                    System.out.println("Keystore size " + keyStore.size());
                    Enumeration aliases = keyStore.aliases();
                    while (aliases.hasMoreElements()) {
                        System.out.println(aliases.nextElement());
                    }
                    Key key = keyStore.getKey(peername, peername.toCharArray());
                    System.out.println("Key information " + key.getAlgorithm() + " " + key.getFormat());
                    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key.getEncoded());
                    keyFactory = keyFactory.getInstance("RSA");
                    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
                    System.out.println("La private Key");
                    Certificate cert = keyStore.getCertificate(peername);
                    PublicKey publicKey = cert.getPublicKey();
                    System.out.println("Toma certificado y genera la key publica");
                    ObjectOutputStream oosokpubKey = new ObjectOutputStream(out);
                    oosokpubKey.writeObject(publicKey);
                    JOptionPane.showMessageDialog(null, "Llave publica publicada");
                    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
                    Signature signature = Signature.getInstance("SHA1withRSA", "BC");
                    signature.initSign(privateKey, new SecureRandom());

                    //JOptionPane.showMessageDialog(padre, "Mando OK");
                    File archivoAMandar = new File(padre.RUTA_ARCHIVOS_COMPARTIDOS_DESCARGADOS, nombreArchivo);
                    FileInputStream fis = new FileInputStream(archivoAMandar);
                    File ArchivoEstado = new File("C:/temp/Recuperar/" + "Enviado" + nombreArchivo + peername + ".txt");
                    long tamArchivo = archivoAMandar.length();
                    long tamencrip = TAM_PAQUETES / 4;

                    long numeroPaquetes = tamArchivo / TAM_PAQUETES;
                    if (tamArchivo < TAM_PAQUETES) {
                        byte[] menor = new byte[(int) tamArchivo];
                        fis.read(menor);
                        out.write(menor);
                    } else {
                        for (int i = 0; i < numeroPaquetes; i++) {
                            Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                            rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
                            byte[] paq = new byte[(int) TAM_PAQUETES];
                            fis.read(paq);
                            byte[] pagencodefin = new byte[512];
                            byte[] encodedBytes = new byte[(int) 128];
                            byte[] paqCripTemp = new byte[(int) tamencrip];
                            for (int k = 0; k < 4; k++) {

                                for (int h = k * (int) tamencrip, r = 0; h < (k + 1) * (int) tamencrip; h++, r++) {
                                    paqCripTemp[r] = paq[h];
                                }
                                encodedBytes = rsaCipher.doFinal(paqCripTemp);
                                for (int z = k * 128, cont = 0; z < ((k + 1) * 128); z++, cont++) {
                                    pagencodefin[z] = encodedBytes[cont];
                                }
                            }
                            System.out.println("Comienza a llenar el siguiente subpaquete: ");
                            out.write(pagencodefin);
                            byte[] elmac = mac.doFinal(pagencodefin);
                            out.write(elmac);
                            signature.update(pagencodefin);
                            byte[] sigBytes = signature.sign();
                            out.write(sigBytes);
                            System.out.println("Tamaño del mac-Digest: " + elmac.length);
                            System.out.println("Tamaño de la firma: " + sigBytes.length);
                            System.out.println("Tamaño del paquete final: " + pagencodefin.length);
                            DataOutputStream outdata = new DataOutputStream(new FileOutputStream(ArchivoEstado, false));
                            outdata.write((peername + ";" + nombreArchivo + ";" + tamArchivo + ";" + i).getBytes());
                            ipaquete = i;
                            System.out.println("iteracion:" + ipaquete);
                        }
                    }
                    long loQuefalta = archivoAMandar.length() - (numeroPaquetes * TAM_PAQUETES);
                    if (loQuefalta > 0) {
                        byte[] falta = new byte[(int) loQuefalta];
                        fis.read(falta);
                        out.write(falta);
                        ipaquete = ipaquete + 1;
                    }

                    fis.close();
                    socket.close();
                    listaPeers.remove(peername);
                    NumClienteON--;
                    //JOptionPane.showMessageDialog(padre, "Se removio:"+listaPeers.size());
                } else if (mensaje.startsWith("SOLICITO_K_MEJORES")) {
                    System.out.println("Recibio como mensaje: Solicito K mejores");
                    String tamar1 = mensaje.split(";")[1];
                    double tamar = (Double.parseDouble(tamar1));
                    double limInferior5 = tamar * 0.9;
                    double limSuperior5 = tamar * 1.1;
                    double limInferior0 = tamar * 0.5;
                    double limSuperior0 = tamar * 1.5;
                    File[] ArchivosK = (new File(padre.RUTA_ARCHIVOS_COMPARTIDOS_DESCARGADOS)).listFiles();
                    int j = 0;
                    String[] Kmejores = new String[5];
                    for (int i = 0; i < ArchivosK.length && j < 5; i++) {
                        File file = ArchivosK[i];
                        if (file.isFile()) {
                            long tamArchivo = file.length();
                            String Ranking;
                            if (tamArchivo > limSuperior0 || tamArchivo < limInferior0) {
                                Ranking = "0";
                            } else if (tamArchivo < limSuperior5 && tamArchivo > limInferior5) {
                                Ranking = "5";
                                boolean listo = false;
                                for (int k = 0; k < 5 && !listo; k++) {
                                    if (Kmejores[k] != null) {
                                        String Rank1 = Kmejores[k].split("?")[1];
                                        if (Rank1.equals("1")) {
                                            Kmejores[k] = (file.getName() + "?" + Ranking);
                                            listo = true;
                                        }
                                    }
                                }
                                if (!listo) {
                                    Kmejores[j] = (file.getName() + "?" + Ranking);
                                    j++;
                                }
                            } else {
                                Ranking = "1";
                                Kmejores[j] = (file.getName() + "?" + Ranking);
                                j++;
                            }
                        }
                    }
                    String msnRank = "MSN_RANKING" + ";" + Kmejores[0] + ";" + Kmejores[1] + ";" + Kmejores[2] + ";" + Kmejores[3] + ";" + Kmejores[4];
                    System.out.println("Se envia como mensaje de Ranking: " + msnRank);
                    PrintWriter pw = new PrintWriter(out, true);
                    pw.println(msnRank);

                } else {
                    String msnOcupado = "PIPE OCUPADO";
                    PrintWriter pw = new PrintWriter(out, true);
                    pw.println(msnOcupado);
                    NumClienteON--;
                }

                System.out.println("Conexión cerrada");
            } catch (Exception ie) {
                ie.printStackTrace();
                System.out.println("Se desconecto el peer en la descarga!!, quedó en paquete" + ipaquete);
            }
        }

        public void run() {

            sendAndReceiveData(socket);


        }
    }
}
